package com.wbp.cms.core.util;

import com.google.common.collect.Maps;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.decoder.Version;
import com.wbp.cms.client.common.vo.ZXingOptionalVO;
import com.wbp.cms.core.sdk.qrcode.ByteMatrix;
import com.wbp.cms.core.sdk.qrcode.Encoder;
import com.wbp.cms.core.sdk.qrcode.QRCode;

import javax.imageio.ImageIO;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

 
public class QRCodeUtils {
  
    private static final Logger logger = Logger.getLogger(QRCodeUtils.class) ;
    
    private static final int QUIET_ZONE_SIZE = 4;

    private static final int FONT_SIZE = 12;

    private static final int SPACE = 6;

    public static final int BLACK = 0xFF000000;

    public static final int WHITE = 0xFFFFFFFF;

    /**
     * 生成二维码(宽高自定)
     *
     * @param content
     * @param width
     * @param height
     * @param outputStream
     * @throws com.google.zxing.WriterException
     * @throws java.io.IOException
     */
    public static void encodeQrcode(String content, int width, int height, OutputStream outputStream) throws WriterException, IOException {

        if (StringUtils.isNotBlank(content) && null != outputStream) {
            encodeQrcode(content, width, height, null, null, null, outputStream);
        }
    }

    /**
     * @param content
     * @param width
     * @param height
     * @param optionalVO
     * @param outputStream
     * @throws WriterException
     * @throws IOException
     */
    public static void encodeQrcode(String content, int width, int height, ZXingOptionalVO optionalVO, OutputStream outputStream) throws WriterException, IOException {
        encodeQrcode(content, width, height, optionalVO, null, null, outputStream);
    }

    public static void encodeQrcode(String content, BufferedImage logoImage, String brief, Integer briefFontSize, int width, int height, ZXingOptionalVO optionalVO, OutputStream outputStream) throws WriterException, IOException {
        encodeQrcode(content, logoImage, brief, null, briefFontSize, width, height, optionalVO, outputStream);
    }

    /**
     * @param content
     * @param logoImage
     * @param brief
     * @param briefFontSize
     * @param width
     * @param height
     * @param optionalVO
     * @param outputStream
     * @throws WriterException
     * @throws IOException
     */
    public static void encodeQrcode(String content, BufferedImage logoImage, String brief, Font briefFont, Integer briefFontSize, int width, int height, ZXingOptionalVO optionalVO, OutputStream outputStream) throws WriterException, IOException {

        // 入参校验
        if (StringUtils.isBlank(content) || null == outputStream) {
            throw new IllegalStateException("[content or outputStream is null]");
        }
        // 最大边际的判断
        width = (1200 < width ? 1200 : width);
        height = (1200 < height ? 1200 : height);
        // 设定可选配置项
        Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
        Version setVersion = null;
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); // 字符集，避免乱码问题
        if (null != optionalVO) {
            hints.put(EncodeHintType.ERROR_CORRECTION, optionalVO.getEcLevel()); // 容错率
            hints.put(EncodeHintType.MARGIN, optionalVO.getMargin()); // 边缘
            setVersion = optionalVO.getVersion();
        }
        // 白边
        int quietZone = QUIET_ZONE_SIZE;
        ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
        if (hints != null) {
            ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel) hints.get(EncodeHintType.ERROR_CORRECTION);
            if (requestedECLevel != null) {
                errorCorrectionLevel = requestedECLevel;
            }
            Integer quietZoneInt = (Integer) hints.get(EncodeHintType.MARGIN);
            if (quietZoneInt != null) {
                quietZone = quietZoneInt;
            }
        }
        // 生成二维码
        QRCode code = Encoder.encode(content, errorCorrectionLevel, hints, setVersion);
        ByteMatrix input = code.getMatrix();
        if (input == null) {
            throw new IllegalStateException();
        }
        int inputWidth = input.getWidth();
        int inputHeight = input.getHeight();
        int qrWidth = inputWidth + (quietZone << 1);
        int qrHeight = inputHeight + (quietZone << 1);
        int outputWidth = Math.max(width, qrWidth);
        int outputHeight = Math.max(height, qrHeight);
        // 放大倍数
        int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);

        int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
        int topPadding = (outputHeight - (inputHeight * multiple)) / 2;

        BitMatrix bitMatrix = new BitMatrix(outputWidth, outputHeight);

        for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
            for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
                if (input.get(inputX, inputY) == 1) {
                    bitMatrix.setRegion(outputX, outputY, multiple, multiple);
                }
            }
        }

        // 二维码图片
        BufferedImage qrcode = toBufferedImage(bitMatrix, multiple, optionalVO);
        if (null == qrcode) {
            throw new RuntimeException("[qrcode is null]");
        }
        if (null != logoImage || null != brief) {
            // 计算输出图片高度(包含文字)
            int printHight = computeHeight(brief, briefFontSize, qrcode);
            // 合并LOGO和文字后的二维码图片
            BufferedImage logoQrcode = new BufferedImage(qrcode.getWidth(), printHight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = logoQrcode.createGraphics();
            // 画二维码
            g.drawImage(qrcode, 0, 0, null);
            // 画LOGO,计算最大LOGO
            if (null != logoImage) {
                Version version = code.getVersion();
                Version.ECBlocks ecBlocks = version.getECBlocksForLevel(errorCorrectionLevel);
                int bits = (int) (ecBlocks.getTotalECCodewords() / 2.8 * 8);
                int max = (int) Math.sqrt(bits) * multiple; // multiple为放大倍数
                if (logoImage.getWidth() > max) {
                    Image smallImage = logoImage.getScaledInstance(max, max, Image.SCALE_SMOOTH);
                    g.drawImage(smallImage, qrcode.getWidth() / 2 - max / 2, qrcode.getHeight() / 2 - max / 2, null);
                } else {
                    g.drawImage(logoImage, qrcode.getWidth() / 2 - logoImage.getWidth() / 2, qrcode.getHeight() / 2 - logoImage.getHeight() / 2, null);
                }
            }
            // 画文字
            if (StringUtils.isNotBlank(brief) ) {
                // 填充背景
                g.setColor(Color.WHITE);
                g.fillRect(0, qrcode.getHeight(), qrcode.getWidth(), printHight - qrcode.getHeight());
                // 画文字
                g.setColor(Color.BLACK);
                int fontSize = FONT_SIZE;
                if (null != briefFontSize) {
                    fontSize = briefFontSize;
                }
                if (briefFont != null) {
                    g.setFont(briefFont);
                } else {
                    g.setFont(new Font("微软雅黑", Font.PLAIN, fontSize));
                }
                g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
                FontMetrics fm = g.getFontMetrics();
                int briefWidth = fm.stringWidth(brief);
                if (briefWidth < qrcode.getHeight()) { // 一行居中
                    g.drawString(brief, (qrcode.getWidth() - briefWidth) / 2, qrcode.getHeight() + 2 + fontSize);
                } else { // 多行左对齐
                    int offsetLeft = 0, rows = 1, charWidth = 0;
                    StringBuffer sb = new StringBuffer();
                    for (char c : brief.toCharArray()) {
                        charWidth = fm.charWidth(c);
                        if (Character.isISOControl(c) || offsetLeft >= (qrcode.getWidth() - charWidth)) {
                            g.drawString(sb.toString(), 0, qrcode.getHeight() + 2 + fm.getHeight() * rows);
                            rows++;
                            offsetLeft = 0;
                            sb = sb.delete(0, sb.length());
                        }
                        sb.append(c);
                        offsetLeft += charWidth;
                    }
                    if (sb.length() > 0) {
                        g.drawString(sb.toString(), 0, qrcode.getHeight() + 2 + fm.getHeight() * rows);
                    }
                }
            }
            g.dispose();
            // 写入输出流
            ImageIO.write(logoQrcode, "PNG", outputStream);
        } else {
            // 写入输出流
            ImageIO.write(qrcode, "PNG", outputStream);
        }
        outputStream.flush();
        outputStream.close();

    }

    /**
     * 推荐方法，因为ImageIO.read(logoFile)比较费时，尽量重用logoImage
     *
     * @param content      二维码内容
     * @param logoImage    二维码LOGO
     * @param brief        二维码文字说明
     * @param width
     * @param height
     * @param optionalVO
     * @param outputStream
     * @throws WriterException
     * @throws IOException
     */
    public static void encodeQrcode(String content, BufferedImage logoImage, String brief, int width, int height, ZXingOptionalVO optionalVO, OutputStream outputStream) throws WriterException, IOException {
        encodeQrcode(content, logoImage, brief, null, width, height, optionalVO, outputStream);
    }

    /**
     * @param content
     * @param width
     * @param height
     * @param optionalVO
     * @param logoFile
     * @param outputStream
     * @throws WriterException
     * @throws IOException
     */
    public static void encodeQrcode(String content, int width, int height, ZXingOptionalVO optionalVO, File logoFile, String brief, OutputStream outputStream) throws WriterException, IOException {
        BufferedImage bi = null;
        if (null != logoFile) {
            bi = ImageIO.read(logoFile);
        }
        encodeQrcode(content, bi, brief, width, height, optionalVO, outputStream);
    }

    public static void encodeQrcode(String content, BufferedImage logoImage, int width, int height, ZXingOptionalVO optionalVO, OutputStream outputStream) throws WriterException, IOException {
        encodeQrcode(content, logoImage, null, width, height, optionalVO, outputStream);
    }

    /**
     * 计算高度
     *
     * @param brief
     * @param qrcode
     * @return
     */
    private static int computeHeight(String brief, Integer briefFontSize, BufferedImage qrcode) {
        int height = 0;
        if (StringUtils.isBlank(brief)) {
            height = qrcode.getHeight();
        } else { // 文本可能换行
            Graphics g = qrcode.getGraphics();
            int fontSize = FONT_SIZE;
            if (null != briefFontSize) {
                fontSize = briefFontSize;
            }
            Font font = new Font("微软雅黑", Font.PLAIN, fontSize);
            if (!font.getFontName().equals("微软雅黑")) {
                font = new Font("宋体", Font.PLAIN, fontSize);
            }
            g.setFont(font);
            FontMetrics fm = g.getFontMetrics();
            int offsetLeft = 0, rows = 1;
            for (char c : brief.toCharArray()) {
                int charWidth = fm.charWidth(c);
                if (Character.isISOControl(c) || offsetLeft >= (qrcode.getWidth() - charWidth)) {
                    rows++;
                    offsetLeft = charWidth;
                } else {
                    offsetLeft += charWidth;
                }
            }
            height = (qrcode.getHeight() + fm.getHeight() * rows + SPACE);
        }
        return height;
    }

    public static BufferedImage toBufferedImage(BitMatrix matrix, int multiple, ZXingOptionalVO optionalVO) {

        int width = matrix.getWidth();
        int height = matrix.getHeight();

        int[] rect = matrix.getEnclosingRectangle();

        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        int onColor = BLACK;
        int offColor = WHITE;
        Integer pdpInnerColor = null;
        if (optionalVO != null) {
            if (optionalVO.getForegroundColor() != null) {
                onColor = optionalVO.getForegroundColor();
            }
            if (optionalVO.getBackgroundColor() != null) {
                offColor = optionalVO.getBackgroundColor();
            }
            if (optionalVO.getPdpInnerColor() != null && onColor != optionalVO.getPdpInnerColor()) {
                pdpInnerColor = optionalVO.getPdpInnerColor();
            }
        }
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                // 是否在定位符内
                if (pdpInnerColor != null && isInParten(rect, multiple, x, y)) {
                    image.setRGB(x, y, matrix.get(x, y) ? pdpInnerColor : offColor);
                } else {
                    image.setRGB(x, y, matrix.get(x, y) ? onColor : offColor);
                }
            }
        }
        return image;
    }

    /**
     * 二维码定位符内点判断
     *
     * @param rect
     * @param mul
     * @param x
     * @param y
     * @return
     */
    private static boolean isInParten(int[] rect, int mul, int x, int y) {

        int left = rect[0]; // 宽留白
        int top = rect[1]; // 高留白
        int pureWidth = rect[2]; // 纯二维码宽
        int pureHeight = rect[3]; // 纯二维码高

        if (left + 2 * mul <= x && x < left + 5 * mul && top + 2 * mul <= y && y < top + 5 * mul) {
            return true;
        }

        if (left + 2 * mul <= x && x < left + 5 * mul && y <= top + pureHeight - 2 * mul && y > top + pureHeight - 5 * mul) {
            return true;
        }

        if (x <= left + pureWidth - 2 * mul && x > left + pureWidth - 5 * mul && top + 2 * mul <= y && y < top + 5 * mul) {
            return true;
        }

        return false;
    }

    /**
     * 解码
     */
    public static Map<String,String> decode(File fileName) {
      try {
        // Reader reader = new MultiFormatReader();
       // String imgPath = "C:\\"+fileName ;
       // File file = new File(imgPath); 
        BufferedImage image;
        try {
          image = ImageIO.read(fileName);
          if (image == null) {
            logger.error("Could not decode image");
          }
          LuminanceSource source = new BufferedImageLuminanceSource(image);
          BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
          com.google.zxing.Result result;
          Map<DecodeHintType,String> hints = new HashMap<DecodeHintType,String>();
          hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
          // 解码设置编码方式为：utf-8，
          result = new MultiFormatReader().decode(bitmap, hints);
          String resultStr = result.getText();
          logger.error("解析后内容：" + resultStr);
          return  getParametersFromUrl(resultStr) ;
        } catch (IOException ioe) {
          logger.error("exception 1: ",ioe);
        } catch (ReaderException re) {
          logger.error("exception 2: ",re);
        }
      } catch (Exception ex) {
        logger.error("exception 3: ",ex);
      }
      return null ;
    }
    
    private static Map<String,String> getParametersFromUrl(String url){
      Map<String,String> maps =Maps.newTreeMap() ;
      if(StringUtils.isEmpty(url))
         return maps ;
      maps.put("url", url) ;
      int len = url.indexOf("?")  ;
      if(len <=0)
        return maps ;
      String url2 = url.substring(len+1) ;
      String[]  strArray = url2.split("&") ;
      for(String str: strArray){
          String[] array = str.split("=") ;
          maps.put(array[0], array[1]) ;
      }
      
      return maps ;
    }
    
    /**
     * @param args
     */
    public static void main(String[] args) {
      FileOutputStream fos = null ;
        try {
          File  f = new File("d:/tmall3.png" ) ;
          fos  = new FileOutputStream(f) ;
            ZXingOptionalVO optionalVO = new ZXingOptionalVO();
            optionalVO.setEcLevel(ErrorCorrectionLevel.Q);
            //optionalVO.setBackgroundColor(0xFFFFFF);
            //optionalVO.setForegroundColor(0xFFB90F);
            //optionalVO.setPdpInnerColor(0xFF8C00);
            optionalVO.setVersion(Version.getVersionForNumber(15));
            optionalVO.setEcLevel(ErrorCorrectionLevel.H);
            optionalVO.setMargin(1);
            File logoF = new File("d:/222.png") ;   // 最好在 100*100个像素以内，60k大小以内 png格式，如果太长的，decode会有问题，解析不出来 
            BufferedImage bi =  ImageIO.read(logoF) ;
            System.out.println(bi.getWidth() +" -  "+ bi.getHeight() )  ;
           /* QRCodeUtils.encodeQrcode("http://h5.m.taobao.com/1212/kaimenyouli.html?ttid=12ewm516&sprefer=dmm011&scanId=19e6d80e-2909-4829-a5a1-e82e5dc22572-测试", null,
                400, 400,
              optionalVO, fos ); */
            
            
             QRCodeUtils.encodeQrcode("http://h5.m.taobao.com/1212/kaimenyouli.html?ttid=12ewm516&sprefer=dmm011&scanId=19e6d80e-2909-4829-a5a1-e82e5dc22572-测试", bi,
                      400, 400,
                    optionalVO, fos ); 
            System.out.println( QRCodeUtils.decode(f));
            
        } catch (Exception e) {
          logger.error("exception1:",e);
        }finally{
          
            try {
              if(fos !=null)
                 fos.close();
            } catch (IOException e) {
               logger.error("exception2:",e);
            } 
        }
    }
}
